<template>
  <el-select
    ref="elSelectRef"
    v-model="selectData"
    style="width: 100%"
    remoteShowSuffix
    :clearable="clearable"
    :placeholder="placeholder"
    :collapseTags="collapseTags"
    :size="globalSize"
    :disabled="disabled"
    :persistent="persistent"
    :multipleLimit="multipleLimit"
    :collapseTagsTooltip="collapseTagsTooltip"
    :remote="false"
    :automaticDropdown="automaticDropdown"
    :multiple="multiple"
    :filterable="filterable"
    :allowCreate="allowCreate"
    :suffixIcon="suffixIcon"
    :defaultFirstOption="defaultFirstOption"
    :reserveKeyword="reserveKeyword"
    :loading="newLoading"
    :loadingText="loadingText"
    :noMatchText="noMatchText"
    :noDataText="noDataText"
    @change="handleChange"
    @visibleChange="handleVisibleChange"
    @blur="handleBlur"
  >
    <template v-if="valueKey">
      <template v-for="(item, index) in dataList as R" :key="item[valueKey]">
        <slot :item="item" :index="index">
          <el-option :key="(item as any)[valueKey]" :label="(item as any)[labelKey]" :value="(item as any)[valueKey]"></el-option>
        </slot>
      </template>
    </template>
    <template v-else>
      <template v-for="(item, index) in dataList as R" :key="item">
        <slot :item="item" :index="index"><el-option :key="String(item)" :label="String(item)" :value="item"></el-option></slot>
      </template>
    </template>
    <template v-if="$slots.header" #header>
      <slot name="header"></slot>
    </template>
    <template v-if="$slots.footer" #footer>
      <slot name="footer"></slot>
    </template>
    <template v-if="$slots.prefix" #prefix>
      <slot name="prefix"></slot>
    </template>
    <template v-if="$slots.empty" #empty>
      <slot name="empty"></slot>
    </template>
    <template v-if="$slots.tag" #tag>
      <slot name="tag"></slot>
    </template>
    <template v-if="$slots.loading" #loading>
      <slot name="loading"></slot>
    </template>
    <template v-if="$slots.label" #label>
      <slot name="label"></slot>
    </template>
  </el-select>
</template>

<script setup lang="ts" generic="K extends Record<string, unknown>, J extends string | number | boolean, R extends K[] | J[], T extends K | J | R">
  import { isEqual } from '@/utils/data';
  import { useSelectDefaultLabel } from '../use-select-default-label';
  import type { Response } from '@/utils/axios/axios';

  type _$DataInterceptorMethod = false | ((val: Response<any>) => R);
  type Props = {
    /** 是否为持续的(当设置为false时,隐藏选择框时将销毁选择框) */
    persistent?: boolean;
    /** 是否可创建条目 */
    allowCreate?: boolean;
    /** 默认选中的项 */
    modelValue?: T | T[] | string;
    /** 是否可清空 */
    clearable?: boolean;
    /** 内置触发弹出框的选择器组件的占位符 */
    placeholder?: string;
    /** 选择列表的数据源的数组 (优先级比url高) */
    list?: R | null;
    /** 加载中状态 */
    loading?: boolean;
    /** 选择列表的数据源的请求链接 (与list二选一) */
    axios?: (...a: any) => Promise<Response<R>>;
    /** 是否为多选 */
    multiple?: boolean;
    /** 最多可选多少个 */
    multipleLimit?: number;
    /** 内置触发弹出框选择器组件的labelKey */
    labelKey?: string;
    /** 唯一的字段Key(如id 请确保唯一) */
    valueKey?: string;
    /** 额外的参数 */
    param?: Record<string, boolean | number | string | undefined>;
    /** 多选时是否将选中值按文字的形式展示 */
    collapseTags?: boolean;
    /** 当鼠标悬停于折叠标签的文本时，是否显示所有选中的标签。 要使用此属性，collapse-tags属性必须设定为 true */
    collapseTagsTooltip?: boolean;
    /** 组件尺寸 */
    size?: $GlobalSize | undefined;
    /** 是否返回原始整个item还是item下的valueKey字段 ,true代表整个item */
    returnOriginal?: boolean;
    /** 每次请求数据后的回调(这里可以整理数据,且必须返回整理之后的数据) */
    dataInterceptorMethod?: _$DataInterceptorMethod;
    /** 是否禁用 */
    disabled?: boolean;
    /** 是否可搜索 */
    filterable?: boolean;
    /** 当 multiple 和 filterable被设置为 true 时，是否在选中一个选项后保留当前的搜索关键词 */
    reserveKeyword?: boolean;
    /** 是否在输入框按下回车时，选择第一个匹配项。 需配合 filterable 或 remote 使用 */
    defaultFirstOption?: boolean;
    /** 对于不可搜索的 Select，是否在输入框获得焦点后自动弹出选项菜单 */
    automaticDropdown?: boolean;
    /** 从服务器加载数据时显示的文本，默认为“Loading” */
    loadingText?: string;
    /** 搜索条件无匹配时显示的文字，也可以使用 empty 插槽设置，默认是 “No matching data'” */
    noMatchText?: string;
    /** 无选项时显示的文字，也可以使用 empty 插槽设置自定义内容，默认是 “No data” */
    noDataText?: string;
    /** 自定义后缀图标组件 */
    suffixIcon?: Component;
  };
  type BaseData = {
    /** 加载的Timeout */
    loadTimeout: boolean;
    /** 弹出框显示隐藏 */
    bubbleVisible: boolean;
    /** 上次请求的参数（用于判断是否需要刷新） */
    oldPostParam: Record<string, boolean | number | string | undefined> | null;
    /** 是否正在加载中 */
    loading: boolean;
  };
  const props = withDefaults(defineProps<Props>(), {
    persistent: false,
    collapseTagsTooltip: true,
    allowCreate: false,
    modelValue: undefined,
    clearable: true,
    placeholder: ' ',
    list: null,
    axios: undefined,
    multiple: false,
    multipleLimit: 0,
    labelKey: '',
    valueKey: undefined,
    param: () => ({}),
    responseKey: 'data',
    collapseTags: false,
    size: undefined,
    returnOriginal: false,
    dataInterceptorMethod: undefined,
    disabled: false,
    filterable: true,
    reserveKeyword: false,
    defaultFirstOption: true,
    automaticDropdown: false,
    loading: undefined,
    loadingText: undefined,
    noMatchText: undefined,
    noDataText: undefined,
    suffixIcon: undefined,
  });
  defineSlots<{
    /** 主体内容 */
    default: (data: { item: R[number]; index: number }) => void;
    /** 下拉列表顶部的内容 */
    header: () => void;
    /** 下拉列表底部的内容 */
    footer: () => void;
    /** Select 组件头部内容 */
    prefix: () => void;
    /** 无选项时的列表 */
    empty: () => void;
    /** select 组件自定义标签内容 */
    tag: () => void;
    /** select 组件自定义 loading内容 */
    loading: () => void;
    /** select 组件自定义标签内容 */
    label: () => void;
  }>();

  const emit = defineEmits<{
    (e: 'blur', event: FocusEvent): void;
    (e: 'change', changeData: T, item?: any): void;
    (e: 'update:modelValue', value: T): void;
    (e: 'firstLoadCallback', response: any): void;
  }>();

  const globalSize = computed(() => props.size || useGlobalStore().globalSize);

  const elSelectRef: $RefComp<'ElSelect'> = ref(null);

  const baseData: BaseData = reactive<BaseData>({
    loadTimeout: false,
    bubbleVisible: false, // 弹出框显示隐藏
    oldPostParam: null,
    loading: false,
  });
  const newLoading = computed(() => (typeof props.loading === 'boolean' ? props.loading : baseData.loading));
  /** 选中的数据 */
  const selectData = ref<T | T[] | string | undefined>(getSelectData(props.modelValue));
  /** 下拉展示的列表 */
  const dataList = ref<R | null>(props.list);

  watch(
    () => props.modelValue,
    (newVal, oldVal) => {
      selectData.value = getSelectData(newVal) as any;
      nextTick(() => {
        // 防止初始化值显示不正确
        if (!props.persistent && elSelectRef.value && (oldVal === null || oldVal === undefined || (Array.isArray(oldVal) && oldVal.length <= 0))) {
          useSelectDefaultLabel(props.multiple, props.labelKey, props.valueKey, dataList.value || [], newVal, elSelectRef);
        }
      });
    }
  );

  // 参数改变时重新加载
  watch(
    () => props.axios,
    () => {
      initData();
    }
  );

  onMounted(() => {
    // 防止初始化值显示不正确
    if (!props.persistent && elSelectRef.value && props.modelValue !== null && props.modelValue !== undefined) {
      useSelectDefaultLabel(props.multiple, props.labelKey, props.valueKey, dataList.value || [], props.modelValue, elSelectRef);
    }
  });

  /** 监听失去焦点 */
  function handleBlur(event: FocusEvent) {
    emit('blur', event);
  }
  /** 得到数据 */
  function getSelectData(val: T | T[] | string | null | undefined): T | string | undefined {
    if (val && typeof val === 'object' && props.valueKey && props.valueKey in val) {
      return ((val as K)[props.valueKey] ?? undefined) as T | string | undefined;
    } else if (Array.isArray(val)) {
      const _list = val.map((item) => {
        if (typeof item === 'object' && props.valueKey && props.valueKey in item) {
          return (item as K)[props.valueKey];
        } else {
          return item;
        }
      });
      return _list as any;
    } else {
      return val ?? undefined;
    }
  }

  /**
   * 得到所有数据(网络数据)
   */
  async function getAxiosData(): Promise<R> {
    try {
      baseData.loading = true;
      const response = await props.axios!(props.param);
      baseData.oldPostParam = { ...props.param };
      emit('firstLoadCallback', response);
      const list = typeof props.dataInterceptorMethod === 'function' ? props.dataInterceptorMethod(response) : (response.data as R);
      baseData.loading = false;
      return Promise.resolve(list);
    } catch (error) {
      baseData.loading = false;
      return Promise.reject(error);
    }
  }

  /**
   * 得到并设置需要显示的数组
   */
  async function setDataList() {
    !isEqual(baseData.oldPostParam, props.param) && initData();
    // 只有当baseData.dataList为false才执行,以解决重复加载问题
    if (!dataList.value || (props.list && dataList.value.length <= 0)) {
      if (props.list) {
        dataList.value = (props.list || []) as any;
      } else if (typeof props.axios === 'function') {
        try {
          // 如果不相同就初始化
          dataList.value = (await getAxiosData()) as any;
        } catch (error) {
          baseData.bubbleVisible && ElToast.error(error.msg || '获取数据失败');
        }
      }
    }

    return Promise.resolve(true);
  }

  /**
   * 监听选中值发生改变
   */
  function handleChange(val: T) {
    let item = val;
    if (props.labelKey && props.valueKey) {
      const arr = (Array.isArray(val) ? val : val === null || val === undefined || typeof val === 'boolean' ? ([] as K[]) : [val]) as J[] | K[];
      const list: any = [];
      arr.forEach((v) => {
        const obj = dataList.value?.find((data: Record<string, unknown>) => v === data || v === data[props.valueKey!]) || undefined;
        obj && list.push(obj);
      });
      item = props.multiple ? list : list.length > 0 ? list[0] || '' : '';
    }
    emit('update:modelValue', props.returnOriginal ? item : val);
    emit('change', props.returnOriginal ? item : val, item);
  }

  /**
   * 显示或隐藏
   */
  async function handleVisibleChange(state: boolean) {
    if (state) {
      baseData.bubbleVisible = true;
      await setDataList();
    } else {
      setTimeout(() => {
        baseData.bubbleVisible = false;
      }, 0);
    }
  }

  /**
   * 初始化数据
   */
  function initData() {
    dataList.value = null;
  }
</script>
